1

I have a react application and I need help getting a component to rerender.

The component represents a blog. It lists out all the blogs it pulls from the database. There's a form at the bottom for adding new posts. When I add a new post, I not only send it the backend to be saved to the database, but I add it to the list of blogs on the front end so that it shows up right away in the UI. <-- This is the problem. The post is being added to the frontend array, which is in the state, but it doesn't show up in the UI unless I refresh the page.

Here's my code:

submitHandler(event) {
        event.preventDefault();

        let formData = new FormData();
        formData.append('title', this.postTitleRef.current.value);
        formData.append('body', this.postBodyRef.current.value);
        if (this.state.file) {
            formData.append('image', this.state.file);
        }

        if (this.state.editPostId) {
            formData.append('postId', this.state.editPostId);
            const editPost = this.state.blogPosts.find(p => p.id === this.state.editPostId);
            if (editPost.filename) {
                formData.append('filename', editPost.filename);
            }
            axios.patch('/blogs', formData, {
                    headers: {
                        Authorization: this.state.accessToken,
                        'content-type': 'multipart/form-data'
                    }
                }).then(response => {
                    const existingPost = this.state.blogPosts.find(p => p.id === this.state.editPostId);
                    existingPost.title = this.postTitleRef.current.value;
                    existingPost.body = this.postBodyRef.current.value;
                    existingPost.updatedAt = response.data.updatedAt;
                    existingPost.filename = response.data.filename || existingPost.filename;
                    existingPost.imageUrl = response.data.imageUrl || existingPost.imageUrl;
                    this.state.blogPosts.sort((p1, p2) => p1.updatedAt > p2.updatedAt ? -1 : 1);
                    this.clearForm();
                }).catch(err => {
                    console.log('err=', err);
                });
        } else {
            axios.post('/blogs', formData, {
                headers: {
                    Authorization: this.state.accessToken,
                    'content-type': 'multipart/form-data'
                }
            }).then(response => {
                this.state.blogPosts.unshift({
                    id: response.data.id,
                    createdAt: response.data.createdAt,
                    updatedAt: response.data.createdAt,
                    filename: response.data.filename || null,
                    title: this.postTitleRef.current.value,
                    body: this.postBodyRef.current.value,
                    imageUrl: response.data.imageUrl || null
                });
                this.clearForm();
            }).catch(err => {
                console.log('err=', err);
            });
        }
    }

When I put console logs in to see the contents of this.state.blogPosts, it shows that the new post is indeed added. But the new post doesn't show up on the screen unless I refresh the page.

Does it have anything to do with the fact that I'm calling event.preventDefault()?

Does it have anything to do with the fact that I'm adding the new post to an array in the state (this.state.blogPosts) rather than calling setState(...)?

Not even this.forceUpdate() seems to work.

Can anyone see what I'm doing wrong? Thanks.

2 Answers 2

3

This is because React doesn't know your state is being updated. To let React know, use this.setState

this.setState((state) => ({
  blogPosts: [{/*your new blog*/}, ...state.blogPosts]
}));
Sign up to request clarification or add additional context in comments.

1 Comment

You would think that would do the trick, but it's still not working.
0

Digging a little deeper, I found that in addition to what kkesley suggested, the problem was in my use of useMemo(...). To construct the table of blog posts, I'm using react-table, the construction of which is done by a call to createTable() in render(). The data for the table is built as follows:

    for (var index = 0; index < props.posts.length; index++) {
        const post = props.posts[index];
        dataArray.push({
            titleCol: post.title,
            bodyCol: post.body,
            updatedAtCol: post.updatedAt,
            imageUrlCol: post.imageUrl[0]
        });
    }
    const data = React.useMemo(() => dataArray, []);

...where props.posts is state.blogPosts passed to the react-table component.

The issue was that useMemo was always returning the original dataArray, which only ever contained the original posts in state.blogPosts. I changed that line to include dataArray as a dependency:

const data = React.useMemo(() => dataArray, [dataArray]);

This fixed the problem.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.