Is there a way I can check if the image has loaded before navigating to a page?
My image if loaded from a url then displayed to the background, I get random errors saying ImageBackground is undefined.
Redux shows the image existing in the store, but I still get the error when navigating to the page:
export const RescueMeLandingScreen = (props) => {
const { navigation } = props;
const {
rescueMe: {
rescueMeSplashScreen: {
backgroundImage: { url }, //https://xxx/v3/assets/bltba214d69f78d0dfb/blt90b1f745b5b348ef/62333622893506175b30a63f/xxx.png
buttonText,
informationText,
title: pageTitle,
},
},
} = useSelector((state) => state);
...
return (
<>
<RACPageStructure loading={false} title={pageTitle} isMain>
<ScrollView contentContainerStyle={styles.scollView}>
<ImageBackground source={{ uri: url }} style={styles.imageBackground}>
...
</ImageBackground>
</ScrollView>
</RACPageStructure>
</>
);
};
You could try conditionally rendering the component.
Example:
{ url !== undefined && <ImageBackground>}
If that doesn't work, you could also create a custom component wrapper for your component that would return a default image if the other one is undefined.
Example:
const ImageBackgroundWrapper = () => {
if (url === undefined) {
return ();
} else {
return ();
}
}
Related
I have page where a user can narrow their search using filters. The issue is that when a user clicks on a filter to filter properties by "rent" or "buy", the url does not refresh to reflect the changes. The changes do however appear in the URL, which is what I want, but I have to manually press enter to refresh the page with the specified filters, so that changes would appear.
As you can see in the photo, the properties listed are not "for rent" properties, so the only way to correctly see the rental properties is to manually enter the url: http://localhost:3000/search?purpose=for-rent&minPrice=20000
import {
Flex,
Select,
Box,
} from "#chakra-ui/react";
import { useRouter } from "next/router";
import Image from "next/image";
import { filterData, getFilterValues } from "../utils/filterData";
class searchFilters extends Component {
constructor(props) {
super(props);
this.state = {
filters: filterData,
};
}
handleChange = (filterValues) => {
const path = this.props.params.pathname;
const { query } = this.props.params;
const values = getFilterValues(filterValues);
values.forEach((item) => {
if (item.value && filterValues?.[item.name]) {
query[item.name] = item.value;
}
});
this.props.params.push({ pathname: path, query: query });
};
render() {
const { filters } = this.state;
return (
<Flex bg="gray.100" p="4" justifyContent="center" flexWrap="wrap">
{filters.map((filter) => (
<Box key={filter.queryName}>
<Select
placeholder={filter.placeholder}
w="fit-content"
p="2"
onChange={(e) =>
this.handleChange({ [filter.queryName]: e.target.value })
}
>
{filter.items.map((item) => (
<option value={item.value} key={item.value}>
{item.name}
</option>
))}
</Select>
</Box>
))}
</Flex>
);
}
}
const withParams = (Component) => {
return (props) => <Component {...props} params={useRouter()} />;
};
export default withParams(searchFilters);
As you are using the same component, it will not reload the page. You can detect the param change with useEffect hook and add the refreshig logic within it. This would reload the data as per the new param.
const { query } = useRouter();
useEffect(() => {
// Refresh logic
}, [query.purpose)]);
TL;DR
When a store change triggers a component function, the current component state is ignored/reset, not letting me use its state data to feed the triggered function.
Full Description
This react-native app has a button located in a heading Appbar stack navigator, which must trigger a function that the currently focused Screen has.
The thing is that this screen is very deep within the navigation scheme, thus I decided to use Redux to directly notify the screen that the button has been pressed.
This also means that every time that this button is pressed and a store slice gets dispatched, I can trigger any function only depending on the Screen implementation.
If i use the very same function from a button within the component it works perfectly. However if I call the same function from the redux store change i get this log:
Console Behavior
# component loaded
false
# started writing, this is the component state
h
he
hel
hell
hello
#header button 'create' state change detected
true
#content as viewed by the onPressPublish function
content: ""
Error 400 - Cannot save empty content
#store reset for further use
false
Appbar
export const AppBarStackNavigator = (props) => {
const { toggle } = useSelector(toolbarSelector);
const handleCreatePress = () => {
dispatch(setCreate({ pressed: true }));
}
return (
<Appbar.Header
style={{ backgroundColor: theme.colors.background, elevation: 0 }}
>
<Button
icon="seed"
mode="contained"
// disabled={!contentProps.valid}
onPress={handleCreatePress}
labelStyle={{ color: 'white' }}
style={{
width: 115,
borderRadius: 50,
alignSelf: "flex-end"
}}>
Sembrar
</Button>
</Appbar.Header>
);
}
Store
import { createSlice } from "#reduxjs/toolkit";
export const toolbarSlice = createSlice({
name: 'toolbar',
initialState: {
create: false
},
reducers: {
setCreate(state, action) {
state.create = action.payload.pressed;
}
}
})
export const { setCreate } = toolbarSlice.actions;
export const toolbarSelector = state => state.toolbar
export default toolbarSlice.reducer;
The navigationally-deep component
import { toolbarSelector, setCreate } from '../store/toolbar';
import { useSelector } from 'react-redux';
// import { useFocusEffect, TabActions, useNavigation } from '#react-navigation/native';
export const DeepComponent = (props) => {
const theme = useTheme();
const { create } = useSelector(toolbarSelector);
return (
<ChildComponent {...props} create={create} setCreate={setCreate} style={{ backgroundColor: theme.colors.background }} />
);
};
Its child (where the function is)
import { useDispatch, useSelector } from 'react-redux';
export const ChildComponent = (props) => {
const dispatch = useDispatch();
const [content, setContent] = useState(''));
let payload = {};
const onPressPublish = async () => {
try {
console.log(payload);
payload = {
...payload,
content,
// images <- other component states
}
console.log(payload);
const seed = await api.writeOne(payload);
} catch (error) {
console.log(error);
Alert.alert('Could not publish :(', error.message);
}
navigation.goBack();
}
useEffect(() => {
console.log(props.create)
console.log(content)
if (props.create) {
console.log(content)
onPressPublish();
}
return () => {
dispatch(props.setCreate({ pressed: false }));
};
}, [props.create])
const onTextChange = (value, props) => {
// ...
setContent(value);
// ...
}
return (
<TextInput
mode='flat'
placeholder={inputPlaceholder}
multiline
onChangeText={text => onTextChange(text, props)}
keyboardShouldPersistTaps={true}
autoFocus
clearButtonMode='while-editing'>
<ParsedContent content={content} />
</TextInput>
<Button
disabled={!contentProps.valid}
onPress={onPressPublish}>
{buttonText}
</Button>
)
}
Here are some suggestions to change the code, you still have not provided any code in your question that would make sense (like the payload variable) but this may give you an idea where to go.
When you create an app with create-react-app you should have a linter that tells you when you have missing dependencies in hooks, you should not ignore these warnings:
//create onPressPublish when component mounts
const onPressPublish = useCallback(async (content) => {
try {
//removed payload as it makes no sense to create it
// and never use it anywhere
//removed assigning to seed becasue it is never used anywhere
await api.writeOne({ content });
} catch (error) {
console.log(error);
Alert.alert('Could not publish :(', error.message);
}
navigation.goBack();
}, []);
const { create, setCreate } = props;
useEffect(() => {
if (create) {
//passing content
onPressPublish(content);
}
return () => {
dispatch(setCreate({ pressed: false }));
};
}, [
//correct dependencies without linter warnings
content,
dispatch,
onPressPublish,
create,
setCreate,
]);
im new in react native, and im doing a note block, the problem now its that once i click save, it saves it to the array but when i get back to home screen, where i show the notes that are saved it doesnt show the last one, until i re load the entire project, how can I do to re render it? i have seen that i have to use this.forceUpdate(), but it doesnt working either, heres the code:
this is the home screen, the first screen the user will see, it shows the notes that are saved calling the component Notes
render() {
return (
<>
<View style = {this.styles.container}>
<View>
<Text style = {this.styles.Text}>Welcome to home!</Text>
</View>
<Notes></Notes>
<View style = {this.styles.View}>
<Button title = "Create new note" styles = {this.styles.Button} onPress = {() => this.props.navigation.navigate("Create_note")}></Button>
</View>
<View style = {this.styles.View}>
<Button title = "Notes" styles = {this.styles.Button} onPress = {() =>this.props.navigation.navigate("See_notes")}></Button>
</View>
</View>
</>
);
}
heres the component Notes:
class Notes extends Component {
constructor(props) {
super(props);
this.state = {
array_notes: [],
}
}
componentDidMount() {
this.fetch_notes();
}
fetch_notes = async() => {
try {
const data = await AsyncStorage.getItem("array_notes");
if (data != null) {
const array_notes = JSON.parse(data);
this.setState({array_notes: array_notes});
}else {
console.log("with no data");
}
}catch (error) {
console.log(error);
}
}
render() {
return (
<>
<View style = {this.styles.View}>
<FlatList data = {this.state.array_notes} renderItem = {({item}) => (<Text style = {this.styles.Text}>{item.title}</Text>)} keyExtractor = {(item) => item.title}></FlatList>
</View>
</>
);
}
and heres the create a new note screen, where the user type a new note:
class Create_note extends Component {
constructor() {
super();
this.state = {
title: "",
content: "",
}
}
save_Data = async() => {
try {
const array_notes = await AsyncStorage.getItem("array_notes");
if (array_notes === null) {
const array_notes = [];
await AsyncStorage.setItem("array_notes", JSON.stringify(array_notes));
}else {
const new_note = {'title': this.state.title, 'content': this.state.content};
const array_notes = JSON.parse(await AsyncStorage.getItem("array_notes"));
array_notes.push(new_note);
await AsyncStorage.setItem("array_notes", JSON.stringify(array_notes));
}
}catch(error) {
console.log(error);
}
}
}
render() {
return (
<>
<Text style = {this.styles.Text }>Welcome to Shum Note!</Text>
<View>
<TextInput style = {this.styles.TextInput_title} placeholder = "Title" multiline = {true} maxLength = {80} onChangeText = {(title) => this.setState({title: title})}></TextInput>
<TextInput style = {this.styles.TextInput_content} placeholder = "Content" multiline = {true} onChangeText = {(content) => this.setState({content: content})}></TextInput>
<Button title = "Save" onPress = {this.save_Data}></Button>
</View>
<View style = {this.styles.back_Button}>
<Button title = "Go back home" onPress = {() => this.props.navigation.navigate("Home")}></Button>
</View>
</>
);
}
once i saved the new note and press the go back home it doesnt show the last one until like i said, i reload the entire project, but something curious, is if i go to create_note screen it will re render each time, but it doesnt happend with home, why?
You have to pass in fetch_notes as a prop in Create_note.
this.props.navigation.navigate("Create_note", fetch_notes: this.fetch_notes)
In your Create_note get the function from navigation.
const { fetch_notes} = this.props.route.params; //https://reactnavigation.org/docs/params/
After saving the note you have to call it like this: this.props.fetch_notes()
You can add the.props.navigation.addListener. When you are back from next screen to previous screen API calling because of addListener focus on current screen and UI is render because of state changes.
componentDidMount() {
this.focusListener =
this.props.navigation.addListener("didFocus", () => {
this.fetch_notes()
});
}
I currently have a preview component which has a reloading functionality attached into it using the useState hook. I now want the ability to refresh this component with the same functionality but with an external component. I know that this can be achieved by the useContext API, however i'm struggling to plug it all together.
Context:
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
const PreviewProvider = PreviewContext.Provider;
PreviewFrame:
const PreviewFrame = forwardRef((props, ref) => {
const { height, width } = props;
const classes = useStyles({ height, width });
return (
<Card className={classes.root} ref={ref}>
<div className={classes.previewWrapper} > {props.children} </div>
<div className={classes.buttonContainer}>
<IconButton label={'Refresh'} onClick={props.toggleReload} />
</div>
</Card>
);
});
PreviewFrameWrapped:
<PreviewFrame
toggleReload={props.toggleReload}
height={props.height}
width={props.width}
ref={frameRef}
>
<PreviewDiv isReloading={props.isReloading} containerRef={containerRef} height={height} width={width} />
</PreviewFrame>
const PreviewDiv = ({ isReloading, containerRef, height, width }) => {
const style = { height: `${height}px`, width: `${width}px`};
return !isReloading ?
<div className='div-which-holds-preview-content' ref={containerRef} style={style} />
: null;
};
Preview:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
So now i want to just be able to import the preview component and be able to refresh it using an external button, so not using the one that's already on the <PreviewFrame>.
I ideally want to consume it like this:
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
function PreviewControls () {
let { handleRefresh } = React.useContext(PreviewContext);
return <div><button onClick={handleRefresh}>↺ Replay</button></div>
}
Preview With My Attempt at Wrapping with Provider:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return (<PreviewProvider value={{ reloading: reloading, setReloading: setReloading, handleRefresh: toggleReload }} >
<PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
{/* it works if i put the external button called <PreviewControls> here*/}
</PreviewProvider>
);
}
So yeah as i said in the commented out block, it will work if put an external button there, however then that makes it attached/tied to the Preview component itself, I'm really not sure how to transfer the reloading state outside of the Preview into the Provider. Can someone please point out what i'm missing and what i need to do make it work in the way i want to.
All you need to do is to write a custom component PreviewProvider and store in the state of reloading and toggleReload function there. The preview and previewControls can consume it using context
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
export default function PreviewProvider({children}) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewContext.Provider value={{reloading, toggleReload}}>{children}</PreviewContext.Provider>
}
export default function Preview(props) {
const {reloading, toggleReload} = useContext(PreviewContext);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
function PreviewControls () {
let { toggleReload } = React.useContext(PreviewContext);
return <div><button onClick={toggleReload}>↺ Replay</button></div>
}
Finally using it like
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
today i decided to update the dependencies of my react project and my component Home didn't work anymore, i'm actually working with a apollo client and apollo react hooks, this is mi Home component file:
function Home(props) {
const {
loading,
data: { getPosts: posts }
} = useQuery(FETCH_POSTS_QUERY);
return (
<Grid columns={3} stackable={true} className={loading ? 'loading' : ''}>
<Grid.Row className='page-title'>
<h1>Recent Posts</h1>
</Grid.Row>
<Grid.Row>
{user && (
<Grid.Column>
<PostForm user={user} />
</Grid.Column>
)}
{loading ? (
<Loading />
) : (
posts &&
posts.map(post=> (
<Grid.Column key={post._id} style={{ marginBottom: 20 }}>
<PostCard post={post} />
</Grid.Column>
))
)}
</Grid.Row>
</Grid>
);
}
and i'm getting this error in the browser:
"TypeError: Cannot read property 'getPosts' of undefined"
i'm trying to fix it with this little code variation:
function Home(props){
let posts = '';
const { user } = useContext(AuthContext);
const { loading, data } = useQuery(FETCH_POSTS_QUERY);
if (data) {
posts = data.getPosts;
}
And everything works fine, but if i add a new Post updating the apollo cache, that cache update correctly with old posts and new post, but the frontend didn't show it, only show old posts until i refresh the page manually.
Edit:
This is the code from the PostForm component, i updated the Home component too adding the PostForm:
function PostForm(props) {
const { values, handleChange, handleSubmit } = useForm(createPostCallback, {
title: 'Example Title',
body: ''
});
const [createPost] = useMutation(CREATE_POST_MUTATION, {
variables: values,
update(dataProxy, result) {
const data = dataProxy.readQuery({
query: FETCH_POSTS_QUERY
});
data.getPosts = [result.data.createPost, ...data.getPosts];
dataProxy.writeQuery({
query: FETCH_POSTS_QUERY,
data
});
values.body = '';
}
});
function createPostCallback() {
createPost();
}
Any idea how to fix the first code issue?
Thanks in advance mates!
I fixed same error with defining data as an object {}
just changed the below code by adding = {}
const {
loading,
data: { getPosts: posts } = {}
} = useQuery(FETCH_POSTS_QUERY);
Queries for read and write cache in apollo works in a inmutable way. In order to do that, you have to use a new variable, you're using data to write the cache.
Try doing this:
const [createPost] = useMutation(CREATE_POST_MUTATION, {
variables: values,
update (proxy, result) {
const data = proxy.readQuery({
query: FETCH_POSTS_QUERY
})
const new_post = result.data.createPost //here's the new var
proxy.writeQuery({
query: FETCH_POSTS_QUERY,
data: { getPosts: [new_post, ...data.getPosts] } // here you're using that var to write the cache
})
values.body = ''
}
})
I would take your if statement and set that inside a useEffect so it checks
if data is a truthy onLoad and so you can sync it to whenever data changes.
const [posts, setPosts] = useState([]);
useEffect(() => {
if (data) {
setPosts(data.getPosts);
}
},[data])
if (posts.length === 0) {
return <h3>No posts as of yet</h3>
}