Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test effects #80

Merged
merged 3 commits into from
Oct 16, 2019
Merged

Test effects #80

merged 3 commits into from
Oct 16, 2019

Conversation

mindplay-dk
Copy link
Contributor

@mindplay-dk mindplay-dk commented Oct 16, 2019

Alright, here's a failing test that should demonstrate the problem we discussed in #79 .

As you can see, we're good on useEffect(f, [x]) as well as useEffect(f, []). 馃憤

The problem is with useEffect(f) which seems to trigger some effects and cleanups twice -

test('useEffect(f) should run every time', async () => {
  let effects = []
  let effectNum = 1

  const effect = () => {
    const currentEffectNum = effectNum++

    effects.push(`effect ${currentEffectNum}`)

    return () => {
      effects.push(`cleanUp ${currentEffectNum}`)
    }
  }

  const Component = () => {
    effects = []

    useEffect(effect)

    return <div>foo</div>
  }

  await testUpdates([
    {
      content: <Component/>,
      test: () => {
        expect(effects).toEqual(["effect 1"])
      }
    },
    {
      content: <Component/>,
      test: () => {
        expect(effects).toEqual(["cleanUp 1", "effect 2"]) 馃憤
      }
    },
    {
      content: <div>removed</div>,
      test: () => {
        expect(effects).toEqual(["cleanUp 2"]) 馃憥 /* LINE 262 */
      }
    }
  ])
})

As you will see when running the test:

    - Expected
    + Received
    
      Array [
    +   "cleanUp 1",
    +   "effect 2",
        "cleanUp 2",
      ]
        at Object.toEqual [as test] (/mnt/c/workspace/fre/test/render.test.jsx:262:25)

So, at removal, cleanUp 1 and effect 2 are currently both being run a second time!

Note that I did already merge your last commit which didn't fix it.

Hopefully this will help you fix it 馃槉

@yisar
Copy link
Collaborator

yisar commented Oct 16, 2019

I think this is another bug, because I just fixed #79

@yisar yisar merged commit 39aaa2d into frejs:master Oct 16, 2019
@yisar
Copy link
Collaborator

yisar commented Oct 16, 2019

Hey, I don't quite understand the example of this case. Maybe like this?

function App () {
  let effects = []
  let effectNum = 1
  const [state, setState] = useState(true)
  useEffect(() => {
    const currentEffectNum = effectNum++

    effects.push(`effect ${currentEffectNum}`)
    console.log(effects) // remove : ["effect 1", "cleanUp 1", "effect 2"]

    return () => {
      effects.push(`cleanUp ${currentEffectNum}`)
    }
  })
  return (
    <div>
      {state && <div>111</div>}
      <button onClick={() => setState(false)}>remove</button>
    </div>
  )
}

@mindplay-dk
Copy link
Contributor Author

From the React guide, "effects with cleanup":

When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.

With useEffect(f) (without the deps argument) the behavior is to clean up the last effect (if any) and then run the new effect - every time.

So the test runs in 3 steps to demonstrate this behavior.

Step 1: Component gets created - the effect gets triggered:

    {
      content: <Component/>,
      test: () => {
        expect(effects).toEqual(["effect 1"])
      }
    },

Step 2: Component gets updated - the first effect gets cleaned up, and then the second effect gets triggered:

    {
      content: <Component/>,
      test: () => {
        expect(effects).toEqual(["cleanUp 1", "effect 2"])
      }
    },

Step 3: Component is removed - the second effect gets cleaned up:

    {
      content: <div>removed</div>,
      test: () => {
        expect(effects).toEqual(["cleanUp 2"])
      }
    }

@yisar
Copy link
Collaborator

yisar commented Oct 16, 2019

Can you give me a use case? The use case I try here is the same as the result of react, so I think the implementation of fre should be correct now

@mindplay-dk
Copy link
Contributor Author

Here's a demo that prints the effects and clean-ups for each operation:

https://codesandbox.io/s/fre-demo-fgymz

You can switch it between imports from react or preact (which work the same) and a copy of fre that I've just built from master.

If I repeat an exact sequence of clicks with react, preact and fre, and copy the console output from each, react and preact are identical - here's a diff against the output from fre:

image

That's react on the left, and fre on the right.

As you can see, react and preact do not repeat the same effects or clean-ups, but fre does.

You need to revert this change - the test should be correct, and should pass if your output is like the one on the left.

@yisar
Copy link
Collaborator

yisar commented Oct 16, 2019

Thank you very much! I will debug and fix it馃檹馃檹馃檹

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

I fixed this bug. Now it looks the same as react, but I still can't run through the test case. Can you find the reason?

@mindplay-dk
Copy link
Contributor Author

Looks like you still need to roll back the change you made to the test?

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

The test is still not necessarily correct. Can you change the example for differentiation? I fixed the last example.
Don't create tests without use cases, it's not convincing

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

I am very concerned about this bug. Our purpose is to find out the difference between react and fre, not the test is right or not.
Before finding a persuasive use case, the test is uncertain, because we can't run the react test, there is only the react use case.

@mindplay-dk
Copy link
Contributor Author

But I don't see any new commits to anything other than files in the demo folder?

Maybe you didn't push all your changes? (or are they on a branch?)

I just fetched master, ran a build and updated this sandbox - compared output with diff again, and the output is exactly the same as before.

Here they are aligned, side-by-side, for easier comparison:

image

Red lines show effects getting fired twice.

Everything else looks correct, just that sometimes the same effects and clean-up get triggered twice?

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

I commit again, but from here, Remove seems to be OK, Increment is another bug.

@mindplay-dk
Copy link
Contributor Author

Reverse also looks fine.

It looks like effects are triggered twice only during updates involving the useState setter?

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

It seems like this. We could keep up with react as much as possible.

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

The above is a good summary, I think usecomponent will works well, which is amazing!

@mindplay-dk
Copy link
Contributor Author

The above is a good summary, I think useComponent will works well, which is amazing!

Thanks, but it's just a toy for now - a lot more work is required to support all of the life-cycle methods, etc... I think it's possible, though I'm not 100% sure yet.

What's interesting, is this would be the opposite of what Preact does: they have the Component API built into the core, and hooks are an optional add-on - Fre would instead have hooks built into the core, with class-based components as an optional add-on. 馃憖

Another really interesting thing about this, is that, with React/Preact, you have to choose either class-based components or hooks... But with this approach, Fre would actually allow mixing class-based components and hooks - you'd be able to call hooks from the render-method of a Component, which is interesting; for example, this would allow you to gradually move features from a large class into hooks, rather than having to refactor everything all at once.

@yisar
Copy link
Collaborator

yisar commented Oct 17, 2019

But with this approach, Fre would actually allow聽mixing聽class-based components and hooks - you'd be able to call hooks from the聽render-method of a Component.

Ah~as you said, This is really amazing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants